package dipl.algorithm.yap;

import dipl.algorithm.math.curve.BezierCurve;
import dipl.algorithm.math.utility.ApfloatUtils;
import org.apfloat.Apfloat;
import org.apfloat.ApfloatMath;

/**
 * Utility functions calculating bounds from Yap's-paper
 */
public class YapUtilities {

	//
	// PUBLIC METHODS
	//

	/**
	 * Calculates square of lower bound so that if segments of given curves are
	 * smaller than that bound they are either elementary or critical elementary.
	 * The returned value as arbitrary precision
	 * @param L L-constant (given control polygon coefficients are (L,L)-bit floats)
	 * @param n degree of one curve
	 * @param m degree of another curve
	 * @return
	 */
	public static Apfloat CalculateMaxSquareDelta( int L, int n, int m ) {
		Apfloat[] deltas = CalculateSquareDeltas( L, n, m );
		return ApfloatUtils.Min( deltas[0], ApfloatUtils.Min( deltas[1], deltas[2] ) );

	}

	/**
	 * Calculates square of yap-deltas 1,3,4 (see thesis) so that if segments of given curves are
	 * smaller than the minimum of those bounds they are either elementary or critical elementary.
	 * The returned values have arbitrary precision
	 * @param L L-constant (control polygon coefficients are (L,L)-bit floats)
	 * @param n degree of one curve
	 * @param m degree of another curve
	 * @return 3-element array containing deltas
	 */
	public static Apfloat[] CalculateSquareDeltas( int L, int n, int m ) {
		Apfloat[] deltas = new Apfloat[3];

		Apfloat n_f =	new Apfloat( (double)n ).toRadix( 2 );
		Apfloat m_f =	new Apfloat( (double)m ).toRadix( 2 );
		Apfloat mn_f = m_f.multiply( n_f );
		Apfloat max_m_n_f = ApfloatUtils.Max( m_f, n_f );

		// some constants we need
		Apfloat _0_f = new Apfloat( 0.0 ).toRadix( 2 );
		Apfloat _1_f = new Apfloat( 1.0 ).toRadix( 2 );
		Apfloat _2_f = new Apfloat( 2.0 ).toRadix( 2 );
		Apfloat _3_f = new Apfloat( 3.0 ).toRadix( 2 );
		Apfloat _4_f = new Apfloat( 4.0 ).toRadix( 2 );
		Apfloat _5_f = new Apfloat( 5.0 ).toRadix( 2 );
		Apfloat _8_f = new Apfloat( 8.0 ).toRadix( 2 );
		Apfloat _9_f = new Apfloat( 9.0 ).toRadix( 2 );
		Apfloat _13_f =	new Apfloat( 13.0 ).toRadix( 2 );
		Apfloat _16_f =	new Apfloat( 16.0 ).toRadix( 2 );
		Apfloat _m24_f =	new Apfloat( -24.0 ).toRadix( 2 );
		Apfloat _81_f =	new Apfloat( 81.0 ).toRadix( 2 );
		Apfloat _256_f =	new Apfloat( 256.0 ).toRadix( 2 );

		// -----------------
		// calculate delta1²

		// a = ((16^L)(9^n))^n
		Apfloat a = ApfloatMath.pow( ApfloatMath.pow( _16_f, L ).multiply( ApfloatMath.pow( _9_f, n ) ), n );

		// b = ((16^L)(9^m))^m
		Apfloat b = ApfloatMath.pow( ApfloatMath.pow( _16_f, L ).multiply( ApfloatMath.pow( _9_f, m ) ), m );

		// K² = max{13,16(ma)²,16(nb)²}
		Apfloat _4mas = _4_f.multiply( m_f ).multiply( a ); _4mas = _4mas.multiply( _4mas );
		Apfloat _4nbs = _4_f.multiply( n_f ).multiply( b ); _4nbs = _4nbs.multiply( _4nbs );
		Apfloat Ks = ApfloatUtils.Max( _13_f, ApfloatUtils.Max( _4mas, _4nbs ) );
		// N² = (3+2m+2n)²
		//      (   5   )
		Apfloat t1 = _3_f.add( _2_f.multiply( m_f ) ).add( _2_f.multiply( n_f ) );
		Apfloat Ns = ApfloatUtils.BinomialCoefficient( t1, _5_f, _1_f, _0_f ); Ns = Ns.multiply( Ns );

		// D = n²m²(3+4/m+4/n) = mn(3mn+4n+4m)
		Apfloat D	= mn_f.multiply( _3_f.multiply( mn_f ).add( _4_f.multiply( m_f ) ).add( _4_f.multiply( n_f ) ) );

		// t = 8N²K²
		// u = -24(mn)²
		// delta1² = t^(-D)(2^u)
		Apfloat t =	Ns.multiply( Ks ).multiply( _8_f );
		Apfloat u =	_m24_f.multiply( mn_f.multiply( mn_f ) );

		deltas[0] = ApfloatMath.pow( t, D.negate() );
		deltas[0] = deltas[0].multiply( ApfloatMath.pow( _2_f, u ) );

		// -----------------
		// calculate delta4²
		Apfloat p = max_m_n_f;
		// delta4² = (16^(p+2)256^(L)81^(2p)p^(5))^(-2p)
		deltas[1] = ApfloatMath.pow( _16_f, p.add( _2_f ) );
		deltas[1] = deltas[1].multiply( ApfloatMath.pow( _256_f, L ) );
		deltas[1] = deltas[1].multiply( ApfloatMath.pow( _81_f, p.multiply( _2_f ) ) );
		deltas[1] = deltas[1].multiply( ApfloatMath.pow( p, _5_f ) );
		deltas[1] = ApfloatMath.pow( deltas[1], p.multiply( _2_f ).negate() );

		// -----------------
		// calculate delta6²
		Apfloat d = new Apfloat( (double)Math.max(  n-1, 2*n-3 ) ).toRadix( 2 );
		Apfloat c = ApfloatMath.pow( _2_f.multiply( n_f ), _4_f );
		c = c.multiply( ApfloatMath.pow( _16_f, L ) );
		c = c.multiply( ApfloatMath.pow( _81_f, n_f ) );
		c = ApfloatMath.pow( c, n_f );
		// K² = max{13,(4na)²,c²}
		Apfloat _4nas = _4_f.multiply( n_f ).multiply( a ); _4nas = _4nas.multiply( _4nas );
		Apfloat cs = c.multiply( c );
		Ks = ApfloatUtils.Max( _13_f, ApfloatUtils.Max( _4nas, cs ) );
		// N² = (5+2n+2d)²
		//      (   5   )
		t1 = _5_f.add( _2_f.multiply( n_f ) ).add( _2_f.multiply( d ) );
		Ns = ApfloatUtils.BinomialCoefficient( t1, _5_f, _1_f, _0_f ); Ns = Ns.multiply( Ns );
		// D = n²d²(3+4/n+4/d) = nd(3nd+4d+4n)
		Apfloat nd_f = n_f.multiply( d );
		D	= nd_f.multiply( _3_f.multiply( nd_f ).add( _4_f.multiply( d ) ).add( _4_f.multiply( n_f ) ) );

		// t = 8N²K²
		// u = -24(nd)²
		// delta6² = t^(-D)(2^u)
		t =	Ns.multiply( Ks ).multiply( _8_f );
		u =	_m24_f.multiply( nd_f.multiply( nd_f ) );

		deltas[2] = ApfloatMath.pow( t, D.negate() );
		deltas[2] = deltas[2].multiply( ApfloatMath.pow( _2_f, u ) );

		return deltas;
	}
}
